package com.gitee.wsl.data.pool.sample

import com.gitee.wsl.collections.iterator.DoubleEndedRingBufferIterator
import com.gitee.wsl.data.pool.AsyncPool
import com.gitee.wsl.data.pool.Pool
import com.gitee.wsl.data.pool.sample.AlivePool.PoolConfig
import com.gitee.wsl.data.pool.sample.AlivePool.PoolStrategy
import com.gitee.wsl.ext.base.currentTimeMillis
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

class AlivePool<T>(
    private val config: PoolConfig<T>,
    private val onBeforeClose: ((T) -> Unit)? = null,
    private val onAfterClose: ((T) -> Unit)? = null,
    private val instanceCreator: suspend () -> T,
): AsyncPool<T> {

    override val capacity: Int
        get() = items.size

    private val itemsAccessMutex = Mutex()

    private val availableItemsSemaphore = Semaphore(config.maxSize)

    private val items = DoubleEndedRingBufferIterator<InstanceHolder<T>>(
            capacity = config.maxSize,
        )

    override suspend fun acquire(): T{
        availableItemsSemaphore.acquire()

        return try {
            itemsAccessMutex.withLock {
                if (items.size == 0) {
                    // Potential optimisation: instance creation without lock - only when necessary for complexity reasons
                    return@withLock instanceCreator()
                }

                return@withLock if (config.strategy == PoolStrategy.LIFO) {
                    items.getLast()
                } else {
                    items.getFirst()
                }.also {
                        // not in pool anymore
                        it.destructor.cancel()
                    }.instance
            }
        } catch (th: Throwable) {
            availableItemsSemaphore.release()
            throw th
        }
    }

    override suspend fun release(instance: T): Boolean {
        return itemsAccessMutex.withLock {
            withContext(NonCancellable) {
                items.putLast(item = createInstanceHolder(instance))
                availableItemsSemaphore.release()
                true
            }
        }
    }

    override suspend fun releaseAll(variables: List<T>) {
        itemsAccessMutex.withLock {
            withContext(NonCancellable) {
                variables.forEach {
                    items.putLast(item = createInstanceHolder(it))
                    availableItemsSemaphore.release()
                }
            }
        }
    }

    override suspend fun dispose() {
            itemsAccessMutex.withLock {
                // the iteration also removes it from the buffer
                for (item in items) {
                    item.destructor.cancel()

                    onBeforeClose?.invoke(item.instance)
                    if (item.instance is AutoCloseable) {
                        item.instance.close()
                    }
                    onAfterClose?.invoke(item.instance)
                }
            }
    }

    @OptIn(ExperimentalUuidApi::class)
    private fun createInstanceHolder(
        instance: T,
    ): InstanceHolder<T> {
        val uid = Uuid.random()
        val flagTime = currentTimeMillis
        val timeToLive = config.keepAliveFor?.toLong(DurationUnit.MILLISECONDS)
        val destructor = config.coroutineScope.launch {
            if (timeToLive == null) {
                return@launch
            }

            delay(timeToLive)

            itemsAccessMutex.withLock {
                val cleanUpCutOff = currentTimeMillis - timeToLive

                while (items.size > 0) {
                    println("check in time :${items.peekFirst().addedAtMillis} , cleanUpCutOff:$cleanUpCutOff")
                    // first is oldest since we add at the last
                    if (items.peekFirst().addedAtMillis > cleanUpCutOff) {
                        break
                    }

                    // this also removes the item from the buffer
                    val item = items.getFirst()

                    // we don't want a dangling job, but we also don't want to cancel ourselves
                    if (item.uid != uid) {
                        item.destructor.cancel()
                    }
                    onBeforeClose?.invoke(item.instance)
                    if (item.instance is AutoCloseable) {
                        item.instance.close()
                    }
                    onAfterClose?.invoke(item.instance)
                }
            }
        }

        return InstanceHolder(
            instance = instance,
            uid = uid,
            destructor = destructor,
            addedAtMillis = flagTime,
        )
    }

    data class PoolConfig<T> @OptIn(DelicateCoroutinesApi::class) constructor(
        /**
         * This is the largest number of objects the pool provides at the same time.
         *
         * Be aware that this creates space for [maxSize] references/pointers.
         */
        val maxSize: Int,
        /**
         * The amount of time the object can spend inside the pool without being used.
         *
         * During cleanup, it is closed, if it implements [AutoCloseable].
         *
         * Be aware that the cleanup blocks the object pool.
         */
        val keepAliveFor: Duration? = null,
        /**
         * The strategy of how the next object to use is determined.
         *
         * @see PoolStrategy
         */
        val strategy: PoolStrategy = PoolStrategy.LIFO,
        /**
         * The coroutine scope in which the cleanup jobs are to run in.
         */
        val coroutineScope: CoroutineScope = GlobalScope,
    )


     enum class PoolStrategy {

        /**
         * **Last-In-First-Out**
         *
         * Works well if you want the lowest number of object in the pool.
         */
        LIFO,

        /**
         * **First-In-First-Out**
         *
         * Works well if you want the lowest number of object creations.
         */
        FIFO,
        ;
    }

    internal class InstanceHolder<T> @OptIn(ExperimentalUuidApi::class) constructor(
        val instance: T,
        val uid: Uuid,
        val destructor: Job,
        val addedAtMillis: Long,
    )
}

fun <T> Pool.Companion.alive(config: PoolConfig<T>,
                             onBeforeClose: ((T) -> Unit)? = null,
                             onAfterClose: ((T) -> Unit)? = null,
                             instanceCreator:  suspend () -> T) = AlivePool(config,onBeforeClose, onAfterClose, instanceCreator)

@OptIn(DelicateCoroutinesApi::class)
fun <T> Pool.Companion.alive(maxSize: Int,
                             keepAliveFor: Duration? = null,
                             strategy: PoolStrategy = PoolStrategy.LIFO,
                             coroutineScope: CoroutineScope = GlobalScope,
                             onBeforeClose: ((T) -> Unit)? = null,
                             onAfterClose: ((T) -> Unit)? = null,
                             instanceCreator: suspend () -> T) = AlivePool(PoolConfig(maxSize,keepAliveFor, strategy,coroutineScope),onBeforeClose, onAfterClose, instanceCreator)